/*
*支持对每个输入框配置
*blur检测 input检测 随时检测((那就是会有个检测函数可以随时调))
*可以定义多种检测策略和错误信息
*支持异步检测，即加入promise机制
*依赖前提检测
*参数函数化
*检测结果缓存
*自定义链式检测
*延迟检测
*等待上一次异步校验完成才能执行下一次校验
id
input
rules[{fn,error}...]
checkOn[input,blur]
preCheckById[id1,id2]
*/
import Utils from '@/utils/Utils.js'
import _ from '@/libs/lodash.min.js'

let CheckInput = function (opts) {
  this.caches = {}
  this.oList = {}
  this.ids = []
  this.rules = []
  this.opts = opts
  this.waitingQueue = []
  this.init(opts)
}
CheckInput.prototype = {
  init (opts) {
    this.rules = opts.rules
    this.extend(opts.list)
    this.bindEvent(opts.list)
  },
  // 拓展一些属性和方法
  extend (list) {
    list.forEach((item) => {
      // 保存所有id
      this.ids.push(item.id)
      // 把list转成对象，便于用id访问item
      this.oList[item.id] = item
      // 添加check函数
      item.check = () => {
        this.enterBranch(item)
      }
    })
  },
  // 绑定事件
  bindEvent (list) {
    // 遍历item.checkOn
    list.forEach((item) => {
      _.isArray(item.checkOn) && item.checkOn.forEach((event) => {
        if (item.input._uid) {
          document.getElementById(item.id).addEventListener(event, (event) => {
            this.enterBranch(item)
          })
        } else {
          item.input.addEventListener(event, (event) => {
            this.enterBranch(item)
          })
        }
      })

      // 遍历item.rules[i].checkOn
      _.isArray(item.rules) && item.rules.forEach((rule) => {
        _.isArray(rule.checkOn) && rule.checkOn.forEach((event) => {
          // 由于item.rules[i]本身就是只有一条rule，故只需要检测该rule，为了能适配已有的函数并不修改原来的规则
          // 故伪造一个新的item，rules里面只有一条rule
          let item2 = _.cloneDeep(item)
          item2.rules = [rule]
          item.input.addEventListener(event, (event) => {
            this.enterBranch(item2)
          })
        })
      })
    })
  },
  // 进入分支，一是进入链式校验，二是直接校验
  enterBranch (item) {
    // 是否有前置检测
    if (_.isArray(item.preCheckIds)) {
      this.chainCheck(item.preCheckIds.concat(item.id), {isWait: item.isWait}) // 原item也加入链里
    } else {
      this.enterCheck(item)
    }
  },
  // 开始校验单个输入框
  enterCheck (item) {
    _.isFunction(this.opts.beforeCheck) && this.opts.beforeCheck()
    _.isFunction(item.beforeCheck) && item.beforeCheck()
    this.isChecking = true
    // 从缓存里读取
    /* if (this.hasCache(item)) {
      let cache = this.caches[item.id]
      this.delay(item, item.rules[cache.ruleIndex]).then(() => {
        this.onchecked(cache.result, item, cache.ruleIndex)
      })
    } else {
      this.checkRuleByIndex(item.rules, 0, item)
    } */

    this.checkRuleByIndex(item.rules, 0, item)
  },
  // 一个输入框有多个校验规则
  // 用函数互调的方式代替for，是为了要避免处理for中断这种麻烦操作（比如each return false、continue、break），因为isValid是false的话是要立即退出的
  checkRuleByIndex (rules, ruleIndex, item) {
    let rule = rules[ruleIndex]

    // 延迟检测
    this.delay(item, rule).then(() => {
      // 调用rule后，判断是否是promise
      let {type: validityType, isValidOrPromise} = this.callRule(rule, item)
      // 若rule返回值是promise则走promise流程，否则走正常流程
      if (validityType === 'promise') {
        this.dealPromiseValidity(isValidOrPromise, rule, ruleIndex, rules, item)
      } else {
        this.dealValidity(isValidOrPromise, rule, ruleIndex, rules, item)
      }
    })
  },
  // 校验单个rule
  callRule (rule, item) {
    let val = item.input.value
    let isValidOrPromise

    if (_.isFunction(this.opts.trimValue)) val = this.opts.trimValue(val)

    // 若是字符串，则从rules里寻找正则
    if (_.isString(rule.rule)) rule.rule = this.rules[rule.rule]

    // rule必须是正则/函数
    if (_.isRegExp(rule.rule)) {
      isValidOrPromise = rule.rule.test(val)
    } else {
      isValidOrPromise = rule.rule(val)
    }
    let type = (isValidOrPromise instanceof Promise) ? 'promise' : 'normal'

    return {
      isValidOrPromise,
      type
    }
  },
  // 处理单个rule的校验结果
  dealValidity (isValid, rule, ruleIndex, rules, item) {
    let isLast = ruleIndex === rules.length - 1
    if (rule.isReverseRule) isValid = !isValid // 反转规则
    let result = {
      isValid: isValid,
      error: rule.error
    }

    // 更新缓存
    this.updateCache(item.id, item.input.value, result, ruleIndex)

    if (!isValid || isLast) {
      this.onchecked(result, item, ruleIndex)
    } else {
      this.checkRuleByIndex(rules, ++ruleIndex, item)
    }
  },
  // 处理单个promise类型的rule的校验结果
  dealPromiseValidity (p, rule, ruleIndex, rules, item) {
    let p2 = Utils.makePromise()

    p2.then((isValid) => {
      this.dealValidity(isValid, rule, ruleIndex, rules, item)
    })

    p
    .then((isValid) => {
      p2.resolveHandle(isValid)
    })
    .catch((isValid) => {
      p2.resolveHandle(isValid)
    })
  },
  // 单个输入框校验完毕（即校验了一个输入框的所有rule）
  onchecked (result, item, ruleIndex) {
     // 通过则没有error
    if (result.isValid) {
      delete result.error
    }
    result = {...result, id: item.id, ruleIndex}
    _.isFunction(item.onchecked) && item.onchecked(result)
    // 校验完毕
    this.isChecking = false
    // 若有等待队列则执行一个链式校验
    if (this.waitingQueue.length) {
      let chainParams = this.waitingQueue.pop()
      this.chainCheck(chainParams.ids, chainParams.opts)
    }
  },
  updateCache (id, val, result, ruleIndex) {
    this.caches[id] = {
      ruleIndex,
      val,
      result
    }
  },
  // 查检缓存，若返回false，表示和缓存一样，否则和缓存不一样，若一样则不再判断，判断的依据是id是否已保存在caches里和val是否一样
  hasCache (item) {
    let id = item.id
    let val = item.input.value
    if (this.caches[id] && this.caches[id].val === val) return true
    return false
  },
  // 返回延时promise
  delay (item, rule) {
    let p = Utils.makePromise()
    let t = rule.delay || item.delay || this.opts.delay || 0
    if (t) {
      setTimeout(() => {
        p.resolveHandle()
      }, t)
    } else {
      p.resolveHandle()
    }
    return p
  },
  // 链式检测：1 2 3 4...9，从左开始检测
  // 1的onchecked方法要执行enterCheck(2)
  // 2的onchecked方法要执行enterCheck(3)
  // 3 4 5 6 7 8执行相同的操作
  // 若检测不通过，则直接执行9的this.onchecked()
  // 9不做任何操作，因为9的操作已经在8的onchecked里了
  // 以上分配完毕后执行第一个1的enterCheck
  chainCheck (ids, opts = {}) {
    // 如果需要等待校验，则添加到队列
    if (this.isChecking && opts.isWait) {
      this.waitingQueue.push({ids, opts})
      return
    }

    let p = Utils.makePromise()
    let item
    let firstItem
    let lastItem
    let nextItem
    let lastI
    let chain = this.generateChain(ids)

    lastI = chain.length - 1

    // 修改每项的onchecked为执行下一项校验（除了最后一项）
    for (let i = 0; i < chain.length; i++) {
      item = chain[i]
      nextItem = chain[i + 1]
      i === 0 && (firstItem = item)
      i === lastI && (lastItem = item)

      // 最后一个不修改onchecked
      if (i === lastI) {
        // 若hasPromise为真，则此链接校验最后一项的onchecked为返回promise，通常用于checkAllInOrder和chackByIdsInOrder
        if (opts.hasPromise === 'hasPromise') {
          item.onchecked = (result) => {
            p.resolveHandle(result)
          }
        }
        break
      }

      // 修改onchecked
      ;((item, nextItem, i, lastI) => {
        item.onchecked = (result) => {
          if (result.isValid) {
            this.enterCheck(nextItem) // 检测通过则执行下一个item
          } else {
            item.onchecked = lastItem.onchecked
            this.onchecked(result, item, result.ruleIndex) // 检测不通过则执行原onchecked
          }
        }
      })(item, nextItem, i, lastI)
    }
    this.enterCheck(firstItem)

    if (opts.hasPromise === 'hasPromise') return p
  },
  // 生成链接校验
  generateChain (ids) {
    let chain = []
    ids.forEach((id, i) => {
      chain.push(_.cloneDeep(this.oList[id]))
    })
    return chain
  },
  // 获取单个输入框项（即一个输入框的所有校验参数）
  getItemById (id) {
    return this.oList[id]
  },
  // 链式校验所有输入框
  checkAllInOrder (opts = {}) {
    return this.chainCheck(this.ids, {hasPromise: 'hasPromise', isWait: opts.isWait})
  },
  // 根据ids进行链式校验
  chackByIdsInOrder (ids, opts = {}) {
    return this.chainCheck(ids, {hasPromise: 'hasPromise', isWait: opts.isWait})
  }
}

export default CheckInput
